using System;
using System.Linq;
using GeneticAlgorithms.ComponentModel;
using GeneticAlgorithms.Factories;
using GeneticAlgorithms.Genomes;
using GeneticAlgorithms.Operators.Crossover;
using GeneticAlgorithms.Operators.Fitness;
using GeneticAlgorithms.Operators.Initialization;
using GeneticAlgorithms.Operators.Mating;
using GeneticAlgorithms.Operators.Mutation;
using GeneticAlgorithms.Operators.Selection;

namespace GeneticAlgorithms.Populations
{
    /// <summary>
    /// Represents a fixed-populationSize genetic population.
    /// </summary>
    /// <typeparam name="TGenome">The type of the genome.</typeparam>
    /// <typeparam name="TGene">The type of the gene.</typeparam>
    [FriendlyName("Fixed-size population")]
    public class FixedSizePopulation<TGenome, TGene> : PopulationBase<TGenome, TGene> where TGenome : IGenome<TGene>
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="FixedSizePopulation&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeFactory">The genome factory.</param>
        /// <param name="getFitness">A delegate to a fitness calculation function.</param>
        public FixedSizePopulation(
            int populationSize,
            IGenomeFactory<TGenome> genomeFactory,
            Func<TGenome, double> getFitness
            )
            : this(
            populationSize,
            genomeFactory,
            new FitnessLambdaWrapper<TGenome, TGene>(getFitness)
            )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FixedSizePopulation&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeFactory">The genome factory.</param>
        /// <param name="fitnessProvider">The fitness provider.</param>
        public FixedSizePopulation(
            int populationSize,
            IGenomeFactory<TGenome> genomeFactory,
            IFitnessProvider<TGenome, TGene> fitnessProvider
            )
            : this(
            populationSize,
            genomeFactory,
            fitnessProvider,
            new SimpleInitializer<TGene>(),
            new RouletteSelector<TGenome, TGene>(),
            new SimpleMater<TGenome, TGene>(),
            new SinglePointCrossover<TGenome, TGene>(),
            new FlipMutator<TGene>()
            )
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FixedSizePopulation&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeFactory">The genome factory.</param>
        /// <param name="fitnessProvider">The fitness provider.</param>
        /// <param name="initializer">The initializer.</param>
        /// <param name="selector">The selector.</param>
        /// <param name="mater">The mater.</param>
        /// <param name="crossover">The crossover.</param>
        /// <param name="mutator">The mutator.</param>
        public FixedSizePopulation(
            int populationSize,
            IGenomeFactory<TGenome> genomeFactory,
            IFitnessProvider<TGenome, TGene> fitnessProvider,
            IGeneticInitializer<TGene> initializer,
            IGeneticSelector<TGenome, TGene> selector,
            IGeneticMater<TGenome, TGene> mater,
            IGeneticCrossover<TGenome, TGene> crossover,
            IGeneticMutator<TGene> mutator)
            : base(
                new TGenome[populationSize], 
                genomeFactory, 
                fitnessProvider, 
                initializer, 
                selector, 
                mater,
                crossover, 
                mutator)
        {
        }

        #endregion

        #region Properties and fields

        /// <summary>
        /// Gets the new populationSize of the generation.
        /// </summary>
        /// <value>The new populationSize of the generation.</value>
        protected internal override int NewGenerationSize { get { return Count; } }

        #endregion

        /// <summary>
        /// Gets the best genomes in the population.
        /// </summary>
        /// <returns>
        /// An array containing the best genomes in the population.
        /// </returns>
        protected TGenome[] GetElite()
        {
            return GetElite(EliteSize);
        }

        /// <summary>
        /// Gets the best genomes in the population.
        /// </summary>
        /// <returns>
        /// An array containing the best genomes in the population.
        /// </returns>
        protected TGenome[] GetElite(int eliteSize)
        {
            return Genomes.OrderByDescending(g => g.Fitness).Take(eliteSize).ToArray();
        }

        /// <summary>
        /// Merges the specified children.
        /// </summary>
        /// <param name="children">The children.</param>
        protected override void Merge(TGenome[] children)
        {
            int i, j;
            // TODO: this code can be greatly optimized
            // look into it once things are working
            if (EliteSize > 0)
            {
                //TGenome[] elite = new TGenome[EliteSize];
                // getting the best genomes from the old generation
                for (i = 0; i < EliteSize; ++i)
                {
                    int bestPos = i;
                    for (j = i + 1; j < Count; ++j)
                    {
                        if (Genomes[j].Fitness > Genomes[bestPos].Fitness)
                            bestPos = j;
                    }
                    TGenome best = Genomes[bestPos];
                    Genomes[bestPos] = Genomes[i];
                    Genomes[i] = best;
                    //elite[value] = best;
                }

                for (i = 0; i < EliteSize; ++i)
                {
                    int worstPos = 0;
                    for (j = 1; j < children.Length; ++j)
                    {
                        if (children[j].Fitness < children[worstPos].Fitness)
                        {
                            worstPos = j;
                        }
                    }

                    // putting one of the elite genomes in the place of the
                    // worst child
                    if (children[worstPos].Fitness < Genomes[i].Fitness)
                    {
                        children[worstPos] = Genomes[i];
                    }
                    else
                    {
                        break;
                    }
                }
            }


            int max = Math.Min(children.Length, Count);
            for (i = 0; i < max; i++)
            {
                this[i] = children[i];
            }
        }

        /// <summary>
        /// Sorts all genomes in the population, according to their fitness.
        /// </summary>
        public override void Sort()
        {
            TGenome[] array = (TGenome[])Genomes;
            Array.Sort<TGenome>(array, (g1, g2) => g1.Fitness.Value.CompareTo(g2.Fitness.Value));
        }
    }
}
